<?php
/**
* All class-loading in a Buan application is routed through this class.
* Class filenames must match the class name.
*
* @package Buan
*/
namespace Buan;
class AutoLoader {

	/**
	* A list of all folder paths that will be searched when loading a class.
	*
	* @var array
	*/
	static private $classPaths = array();

	/**
	* Attempts to find and include a source file for the specified class.
	* If not found, it throws a \Buan\Exception, which is caught and handled by
	* "__autoload()".
	*
	* @param string Name of the class to load (case-sensitive)
	* @return void
	* @throws \Buan\Exception
	*/
	static public function loadClass($className) {

		// Apply namespace alterations
		// At present this will simply remove all \ characters, so "Buan\Config"
		// would become "BuanConfig"
		$className = str_replace("\\", "", $className);

		// Search all class paths
		foreach(self::$classPaths as $idx=>$path) {
			if(is_file("$path/$className.php")) {
				include_once("$path/$className.php");
				reset(self::$classPaths);
				return TRUE;
			}
		}

		// If the class wasn't found, throw a Buan\Exception
		throw new \Buan\Exception("Class '$className' was not found.");
	}

	/**
	* Adds a folder path to the list of paths that are searched when loading
	* classes. The filenames of each class must match the class name exactly, eg
	* a class named "HelloWorld" must be stored in a file named "HelloWorld.php"
	*
	* @todo Could add another parameter here, $regex, which you could use to
	* indicate what filename matches are stored in the given path, so the
	* searching routine would only bother searching in $path if the given class
	* name matches this $regex (if $regex is ommitted then any filename would be
	* assumed).
	*
	* @param string Absolute path to a folder containing classes
	* @return void
	*/
	static public function addClassPath($path) {

		// Add the specified path to the list of paths to be searched when loading a class
		self::$classPaths[] = $path;
	}
}

/**
* @todo The exception handling doesn't seem to work if we register
* Buan\AutoLoader::loadClass() directly in spl_autload_register(), but using
* this global "inbetweener" function does. Might it be the fact that this
* routine defines a new class within the scope of a class method?
*
* @param string Class to load
* @return void
*/
function fAutoLoader($className) {

	// Try to load class
	try {
		\Buan\AutoLoader::loadClass($className);
	}

	// Capture exception
	catch(\Buan\Exception $e) {

		// Check for any other custom autoloaders that have been registered via
		// SPL and execute each one in turn in an effort to load the required
		// class.
		$customAutoloaders = array_diff(spl_autoload_functions(), array(__FUNCTION__));
		if(count($customAutoloaders)>0) {
			foreach($customAutoloaders as $al) {
				is_array($al) ? $al[0]::$al[1]($className) : $al($className);
				if(class_exists($className, FALSE) || interface_exists($className, FALSE)) {
					return TRUE;
				}
			}
		}

		// If the class has still not been defined, auto-define it here.
		// This will throw the Exception message $e.
		// NOTE: The $count variable is used to ensure that the Exception is
		// only unserialized once, other we get offset errors.
		if(!class_exists($className, FALSE) && !interface_exists($className, FALSE)) {
			$eserial = preg_replace("/\'/", "\'", serialize($e));
			return eval("use Buan\SystemLog;class $className {
				function __construct(\$e=NULL) {
					static \$count = 0;
					\$count++;
					if(\$count==1) {
						\$e = unserialize(\$e);
						throw \$e;
					}
				}
	
				function __call(\$method, \$args) {
					SystemLog::add('Trying to call a method after class was not found, \"$className::\$method\".', SystemLog::WARNING);
				}
			}
			new $className('$eserial');");
		}
	}
	return TRUE;
}

// Register the Buan\fBuanAutoLoader() function as a PHP autoloader.
spl_autoload_register(__NAMESPACE__.'\fAutoLoader');
?>